package com.google.code.irouter.batch;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.JDOUserException;

import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.code.irouter.batch.api.Sender;
import com.google.code.irouter.model.Consumer;
import com.google.code.irouter.model.History;
import com.google.code.irouter.model.Model;
import com.google.code.irouter.model.Queue;
import com.google.code.irouter.utils.ApiUtil;
import com.google.code.irouter.utils.CacheUtil;
import com.google.code.irouter.utils.ClassUtil;
import com.google.code.irouter.utils.DateTimeUtil;
import com.google.code.irouter.utils.LocalUtil;
import com.google.code.irouter.utils.LogUtil;
import com.google.code.irouter.utils.ModelUtil;

public final class Dispatcher {
	
	private static String END_POINT = "com.google.code.irouter.batch.Dispatcher.END_POINT";
	
	private static long DAY = 24L * 60L * 60L * 1000L;
	
	private static long HOUR = 1L * 60L * 60L * 1000L;
	
	private static void updateHistory(Queue queue, History.stat state) {
		try {
			History history = (History) ModelUtil.select(History.class, queue.getHistory()).get(0);
			history.setState(state.name());
			history.pushUpdateDate();
			ModelUtil.save(history);
		} catch (Exception exception) {
			LogUtil.error(
					LogUtil.TYPE.BATCH, 
					Dispatcher.class, 
					queue.getSolution(), 
					LocalUtil.getResource("i-router.batch.common.unknown"), 
					exception
			);
		}
	}
	
	private static void removeHistory(Queue queue) {
		try {
			History history = (History) ModelUtil.select(History.class, queue.getHistory()).get(0);
			ModelUtil.remove(history);	
		} catch (Exception exception) {
			LogUtil.error(
					LogUtil.TYPE.BATCH, 
					Dispatcher.class, 
					queue.getSolution(), 
					LocalUtil.getResource("i-router.batch.common.unknown"), 
					exception
			);
		}
	}
	
	private static void clean(boolean isSuccess, Queue queue){
		try {
			if(isSuccess){
				Consumer consumer = (Consumer) ModelUtil.select(Consumer.class, queue.getConsumer()).get(0);
				consumer.setLastCount(consumer.getLastCount() + 1);
				ModelUtil.save(consumer);
				
				updateHistory(queue, History.stat.END);
				ModelUtil.remove(queue);
			}else{
				Date createTime = queue.getCreateDate();
				if(DateTimeUtil.getDateInterval(DateTimeUtil.getNow(), createTime)>2 * DAY) {
					LogUtil.info(LogUtil.TYPE.BATCH, Dispatcher.class, queue.getSolution(), LocalUtil.getResource("com.google.code.irouter.batch.dispatcher.queue.clean.old"));
				}
				removeHistory(queue);
				ModelUtil.remove(queue);
			}
		} catch (Exception exception) {
			LogUtil.error(
					LogUtil.TYPE.BATCH, 
					Dispatcher.class, 
					queue.getSolution(),
					LocalUtil.getResource("i-router.batch.common.unknown"), 
					exception
			);
		}
	}
	
	public static void execute() {	
		Object cache = CacheUtil.get(END_POINT);
		if(cache != null) {
			Calendar now = Calendar.getInstance();
			Calendar end = (Calendar) cache;
			if(end.compareTo(now) > 0)
				return;
		}
		continued();			
	}

	public static void continued() {	
		List<Model> objectList = new ArrayList<Model>();
		try {
			Map<String, SortDirection> order = new HashMap<String, SortDirection>();
			order.put("createDate", SortDirection.ASCENDING);
			objectList = ModelUtil.selectByOrder(Queue.class, order);
			if(objectList.size() > 0) {
				execute(objectList.get(0));
			}
		} catch (Exception exception) {
			LogUtil.error(
					LogUtil.TYPE.BATCH, 
					Dispatcher.class, 
					null,
					LocalUtil.getResource("i-router.batch.common.unknown"), 
					exception
			);
		}

		if(objectList.size() > 1) {
			DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
			com.google.appengine.api.taskqueue.Queue queue = QueueFactory.getQueue("dispatch");
			try {
			    Transaction txn = ds.beginTransaction();
			    queue.add(TaskOptions.Builder.withUrl("/cron/dispatch").method(TaskOptions.Method.GET).param("continued", "1").countdownMillis(1000 * 90));
			    txn.commit();
			    
			    Calendar calendar = Calendar.getInstance();
			    calendar.add(Calendar.MINUTE, 3);
			    CacheUtil.put(END_POINT, calendar);
			    
			} catch (DatastoreFailureException exception) {
				LogUtil.error(
						LogUtil.TYPE.BATCH, 
						Dispatcher.class, 
						null,
						LocalUtil.getResource("i-router.batch.common.unknown"), 
						exception
				);
			}
		}
	}

	private static void execute(Model object) {

			Queue queue = (Queue) object;
			try{
				Consumer consumer = (Consumer) ModelUtil.select(Consumer.class, queue.getConsumer()).get(0);
				if(consumer.getQuota() > 0) {
					if(consumer.getQuota() <= consumer.getLastCount()) {
						if(DateTimeUtil.getDateInterval(DateTimeUtil.getNow(), consumer.getLastHour()) >= 1 * HOUR) {
							consumer.setLastHour(DateTimeUtil.getNow());
							consumer.setLastCount(0);
							ModelUtil.save(consumer);
						} else {
							LogUtil.warn(LogUtil.TYPE.BATCH, Dispatcher.class, queue.getSolution(), LocalUtil.getResource("com.google.code.irouter.batch.dispatcher.quota.over"));
							removeHistory(queue);
							ModelUtil.remove(queue);
							return;
						}
					}
				}
				
				try {
					Sender sender = (Sender) ClassUtil.getNewInstance(ApiUtil.get(Sender.class.getSimpleName(), consumer.getSender()).getRefrence());
					sender.execute(queue);
					clean(sender.isSuccess(), queue);
				} catch (ClassNotFoundException exception) {
					LogUtil.warn(
							LogUtil.TYPE.BATCH, 
							Dispatcher.class, 
							queue.getSolution(), 
							LocalUtil.getResource("com.google.code.irouter.batch.dispatcher.queue.missing.sender"), 
							exception
					);
					removeHistory(queue);
					ModelUtil.remove(queue);
				} catch (JDOUserException exception) {
					LogUtil.warn(
							LogUtil.TYPE.BATCH, 
							Dispatcher.class, 
							queue.getSolution(), 
							LocalUtil.getResource("com.google.code.irouter.batch.dispatcher.consumer.removed"), 
							exception
					);
					removeHistory(queue);
					ModelUtil.remove(queue);
				} catch (Exception exception) {
					LogUtil.error(
							LogUtil.TYPE.BATCH, 
							Dispatcher.class, 
							queue.getSolution(), 
							LocalUtil.getResource("i-router.batch.common.unknown"), 
							exception
					);
					removeHistory(queue);
					ModelUtil.remove(queue);
				}
			} catch (JDOObjectNotFoundException exception) {
				LogUtil.warn(
						LogUtil.TYPE.BATCH, 
						Dispatcher.class, 
						queue.getSolution(), 
						LocalUtil.getResource("com.google.code.irouter.batch.dispatcher.consumer.removed"), 
						exception
				);
				removeHistory(queue);
				ModelUtil.remove(queue);
			} catch (Exception exception) {
				LogUtil.error(
						LogUtil.TYPE.BATCH, 
						Dispatcher.class, 
						queue.getSolution(), 
						LocalUtil.getResource("i-router.batch.common.unknown"), 
						exception
				);
				removeHistory(queue);
				ModelUtil.remove(queue);
			}

	}

}
